package com.joshua.spring.configuration;

import com.joshua.spring.integration.IntegrationAuthenticationFilter;
import com.joshua.spring.integration.IntegrationUserDetailServiceImpl;
import com.joshua.spring.integration.exception.IntegrationWebResponseExceptionTranslator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.sql.DataSource;

/**
 * com.joshua.spring.business.configuration -- AuthorizationServerConfiguration
 * <p>
 * description: 通过继承 AuthorizationServerConfigurerAdapter
 * 并且覆写其中的三个configure方法来进行配置
 * <p>
 * OAuth 2.0定义了<a https://www.cnblogs.com/Innocent-of-Dabber/p/11009811.html>四种授权方式</a>
 * 1.简化模式（implicit）
 * 2.授权码模式（authorization code）
 * 3.密码模式（resource owner password credentials）
 * 4.客户端模式（client credentials）(主要用于api认证，跟用户无关)
 * 注入 AuthenticationManager 是为了支持 password 授权模式(也就是上面的第3种)
 * {@link WebSecurityConfiguration#authenticationManagerBean()}
 *
 * @author <a href="mailto:joshualwork@163.com">joshua_liu</a>
 * @date 2020/1/2 11:35
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private IntegrationAuthenticationFilter integrationAuthenticationFilter;

    @Autowired
    private IntegrationUserDetailServiceImpl userDetailService;

    @Autowired
    private IntegrationWebResponseExceptionTranslator exceptionTranslator;


    /**
     * description: 可以设置基于内存或者基于jdbc的clients收集
     * 此处基于 JDBC 实现，需要事先在数据库配置客户端信息
     * 基于内存的clients收集见底部注释
     *
     * @param clients {@link ClientDetailsServiceConfigurer}
     * @author <a href="mailto:joshualwork@163.com">joshua_liu</a>
     * @date 2020/1/2 11:36
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(jdbcClientDetailsService());
    }

    /**
     * description: 用来配置授权authorization模式(这里使用密码模式,见顶注)
     * 以及令牌(token)的访问端点和令牌服务token services
     * 注意，此处不加userDetailService，refreshToken时会报错
     *
     * @param endpoints {@link AuthorizationServerEndpointsConfigurer}
     * @author <a href="mailto:joshualwork@163.com">joshua_liu</a>
     * @date 2020/1/2 11:36
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.exceptionTranslator(exceptionTranslator)
                .authenticationManager(authenticationManager)
                .tokenStore(tokenStore())
                .userDetailsService(userDetailService);
    }

    /**
     * description: 用来配置令牌（token）端点的安全约束。
     *
     * @param security {@link AuthorizationServerSecurityConfigurer}
     * @author <a href="mailto:joshualwork@163.com">joshua_liu</a>
     * @date 2020/1/2 11:36
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                // 允许客户端访问 /oauth/check_token 检查 token
                .allowFormAuthenticationForClients()
                .checkTokenAccess("isAuthenticated()")
                .checkTokenAccess("permitAll()")
                .addTokenEndpointAuthenticationFilter(integrationAuthenticationFilter);
    }

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * @return {@link ClientDetailsService}
     * @author <a href="mailto:joshualwork@163.com">joshua_liu</a>
     * @date 2020/1/2 11:46
     * @see #configure(ClientDetailsServiceConfigurer)
     * 当前类覆写的 configure(ClientDetailsServiceConfigurer)
     */
    @Bean
    public ClientDetailsService jdbcClientDetailsService() {
        return new JdbcClientDetailsService(dataSource());
    }


    /**
     * description: 基于 Redis 实现，令牌保存到 缓存数据库
     *
     * @return {@link TokenStore}
     * @author <a href="mailto:joshualwork@163.com">joshua_liu</a>
     * @date 2020/1/2 14:31
     */
    @Bean
    public TokenStore tokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}


/**
 * description: 基于 内存的clients收集
 *
 * @author <a href="mailto:joshualwork@163.com">joshua_liu</a>
 * @date 2020/1/2 11:44
 * <p>
 * // 定义了两个客户端应用的通行证
 * // 使用in-memory存储
 * clients.inMemory()
 * // client_id
 * .withClient("ben1")
 * // client_secret
 * .secret(new BCryptPasswordEncoder().encode("123456"))
 * // 该client允许的授权类型
 * .authorizedGrantTypes("authorization_code", "refresh_token")
 * // 允许的授权范围
 * .scopes("all")
 * .autoApprove(false)
 * //加上验证回调地址
 * .redirectUris("http://localhost:8086/login")
 * .and()
 * .withClient("ben2")
 * .secret(new BCryptPasswordEncoder().encode("123456"))
 * .authorizedGrantTypes("authorization_code", "refresh_token")
 * .scopes("all")
 * .autoApprove(false)
 * .redirectUris("http://localhost:8087/login");
 */
